Popular Searches
Popular Course Categories
Popular Courses

Creating flexible and responsive layouts

Creating flexible and responsive layouts

Flutter Layout & UI Design

Creating Flexible and Responsive Layouts in Flutter

Flexible and responsive layouts are essential in Flutter applications because the same UI may need to work on different mobile phones, tablets, desktop screens, and web browsers. Flutter provides powerful layout widgets such as Row, Column, Expanded, Flexible, Wrap, LayoutBuilder, and MediaQuery to create layouts that automatically adapt to available screen space.

Flutter's Expanded and Flexible widgets are specifically designed for distributing available space inside Row, Column, and Flex. Expanded forces its child to fill the allocated space, while Flexible allows the child to use less than the allocated space when appropriate.


1. What Is a Flexible Layout?

A flexible layout is a layout that can adjust its size and position according to the available space. Instead of giving every widget a fixed width or height, Flutter can distribute available space dynamically.

For example, consider a screen containing two cards. On a wide screen, the cards can appear side by side. On a narrow screen, the cards can be displayed one below another.

Wide Screen:
+-------------------+-------------------+
|      Card 1       |      Card 2       |
+-------------------+-------------------+

Small Screen:
+---------------------------------------+
|                Card 1                 |
+---------------------------------------+
|                Card 2                 |
+---------------------------------------+

2. What Is a Responsive Layout?

A responsive layout changes its structure, size, spacing, or alignment based on the available screen dimensions.

A responsive Flutter application should be able to provide a usable interface on:

  • Small mobile phones
  • Large mobile phones
  • Tablets
  • Laptops
  • Desktop computers
  • Flutter web browsers

3. Flexible vs Responsive Layout

Flexible LayoutResponsive Layout
Uses available space efficiently.Adapts the UI to different screen sizes.
Commonly uses Row, Column, Expanded and Flexible.Can use LayoutBuilder, MediaQuery, Wrap and responsive breakpoints.
Focuses on space distribution.Focuses on changing the UI according to screen size.
Useful inside individual sections.Useful for complete application screens.

4. Understanding Flutter's Flex Layout System

Flutter uses the Flex layout system for arranging widgets in one direction. Row is a horizontal Flex layout, while Column is a vertical Flex layout.

Flex
├── Row       → Horizontal layout
└── Column    → Vertical layout

The Flex system distributes available space among its children according to properties such as flex, mainAxisAlignment, crossAxisAlignment, and mainAxisSize.

5. Using Expanded for Flexible Width

Expanded makes a child fill the available space along the main axis of a Row, Column, or Flex. Multiple Expanded children divide the remaining space according to their flex values.

Row(
  children: [
    Expanded(
      child: Container(
        height: 100,
        color: Colors.blue,
      ),
    ),
    Expanded(
      child: Container(
        height: 100,
        color: Colors.green,
      ),
    ),
  ],
)

Both containers receive an equal share because both have the default flex: 1.

6. Using Flex Values

The flex property controls how available space is distributed between flexible children.

Row(
  children: [
    Expanded(
      flex: 1,
      child: Container(
        height: 100,
        color: Colors.blue,
      ),
    ),
    Expanded(
      flex: 2,
      child: Container(
        height: 100,
        color: Colors.green,
      ),
    ),
  ],
)

In this example, the available width is divided into three proportional parts:

  • First container = 1 part
  • Second container = 2 parts
  • Total = 3 parts

Therefore, the second container receives approximately twice the width of the first container.

7. Expanded Uses Tight Flex

Expanded uses FlexFit.tight. This means the child is required to fill the space allocated to it.

Expanded(
  flex: 1,
  child: Container(
    color: Colors.blue,
    child: const Center(
      child: Text('Expanded'),
    ),
  ),
)

This behavior is useful when you want UI sections to completely occupy the available space.

8. Using Flexible

Flexible allows a child inside a Row, Column, or Flex to use available space without forcing it to fill the entire allocated area.

Row(
  children: [
    Flexible(
      child: Container(
        padding: const EdgeInsets.all(16),
        color: Colors.blue,
        child: const Text(
          'This text can use available space.',
        ),
      ),
    ),
    const SizedBox(width: 10),
    const Icon(Icons.star),
  ],
)

By default, Flexible uses FlexFit.loose, allowing the child to be smaller than the allocated space.

9. Expanded vs Flexible

FeatureExpandedFlexible
ParentRow, Column or FlexRow, Column or Flex
Default flex11
Default fitFlexFit.tightFlexFit.loose
Must fill allocated space?YesNo
Main purposeFill remaining spaceShare available space while allowing smaller content

10. Creating a Responsive Row

A common responsive pattern is to use Expanded for content that should automatically adjust its width.

Row(
  children: [
    Expanded(
      child: Container(
        padding: const EdgeInsets.all(20),
        color: Colors.blue,
        child: const Text(
          'Left Section',
          textAlign: TextAlign.center,
        ),
      ),
    ),
    const SizedBox(width: 12),
    Expanded(
      child: Container(
        padding: const EdgeInsets.all(20),
        color: Colors.green,
        child: const Text(
          'Right Section',
          textAlign: TextAlign.center,
        ),
      ),
    ),
  ],
)

The two sections automatically share the available horizontal space.

11. Creating a Responsive Column

Expanded can also distribute vertical space when used inside a Column.

Column(
  children: [
    const Text('Header'),
    Expanded(
      child: Container(
        width: double.infinity,
        color: Colors.blue,
        child: const Center(
          child: Text('Flexible Content Area'),
        ),
      ),
    ),
    const Text('Footer'),
  ],
)

The middle section takes the remaining vertical space between the header and footer.

12. Using Spacer for Flexible Spacing

Spacer creates flexible empty space inside a Row, Column, or another Flex layout.

Row(
  children: [
    const Text('Profile'),
    const Spacer(),
    const Icon(Icons.settings),
  ],
)

The Spacer pushes the settings icon toward the right side of the available row.

13. Responsive Text Layout

Long text can cause overflow when placed inside a Row. Wrapping the text with Expanded or Flexible allows Flutter to constrain the text according to the available width.

Row(
  children: [
    const CircleAvatar(
      child: Icon(Icons.person),
    ),
    const SizedBox(width: 12),
    Expanded(
      child: Text(
        'This is a long user name that needs to adapt to the available screen width.',
        maxLines: 2,
        overflow: TextOverflow.ellipsis,
      ),
    ),
  ],
)

This is a common pattern for profile lists, chat applications, product cards, and notification items.

14. Responsive Buttons

Buttons can also use flexible layouts to prevent overflow.

Row(
  children: [
    Expanded(
      child: ElevatedButton(
        onPressed: () {},
        child: const Text('Login'),
      ),
    ),
    const SizedBox(width: 12),
    Expanded(
      child: OutlinedButton(
        onPressed: () {},
        child: const Text('Register'),
      ),
    ),
  ],
)

15. Using Wrap for Responsive Items

Row keeps all children on one horizontal line. If there is not enough space, the content can overflow. Wrap can move children to another line when required.

Wrap(
  spacing: 10,
  runSpacing: 10,
  children: [
    Chip(label: Text('Flutter')),
    Chip(label: Text('Dart')),
    Chip(label: Text('Firebase')),
    Chip(label: Text('UI Design')),
    Chip(label: Text('Mobile')),
  ],
)

Wrap is useful for tags, chips, categories, filters, buttons, and responsive card layouts.

16. Row vs Wrap

RowWrap
One horizontal lineCan create multiple lines
Can overflow if content is too largeMoves children to another run
Useful for toolbars and headersUseful for tags, chips and responsive collections

17. Using MediaQuery for Responsive Layouts

MediaQuery provides information about the current application window, including its size. It can be used when the UI needs to make decisions based on screen dimensions.

Widget build(BuildContext context) {
  final screenWidth = MediaQuery.sizeOf(context).width;

  return Scaffold(
    body: Center(
      child: Text(
        screenWidth < 600
            ? 'Mobile Layout'
            : 'Large Screen Layout',
      ),
    ),
  );
}

18. Creating Breakpoints

Breakpoints are width ranges at which the application changes its layout structure.

Widget build(BuildContext context) {
  final width = MediaQuery.sizeOf(context).width;

  if (width < 600) {
    return const MobileLayout();
  }

  if (width < 1024) {
    return const TabletLayout();
  }

  return const DesktopLayout();
}

These values are examples. Breakpoints should be selected according to the content and design requirements of the application rather than blindly using device names.

19. Using LayoutBuilder

LayoutBuilder is useful when a widget needs to respond to the constraints supplied by its parent.

LayoutBuilder(
  builder: (context, constraints) {
    if (constraints.maxWidth < 600) {
      return const Text('Small Layout');
    }

    return const Text('Large Layout');
  },
)

This approach is particularly useful for reusable components because the component can respond to the space provided by its parent rather than relying only on the overall screen size.

20. Responsive Card Layout

Widget buildCardLayout(double width) {
  if (width < 600) {
    return Column(
      children: [
        buildCard('Card 1'),
        const SizedBox(height: 12),
        buildCard('Card 2'),
      ],
    );
  }

  return Row(
    children: [
      Expanded(child: buildCard('Card 1')),
      const SizedBox(width: 12),
      Expanded(child: buildCard('Card 2')),
    ],
  );
}

Widget buildCard(String title) {
  return Card(
    child: Padding(
      padding: const EdgeInsets.all(20),
      child: Text(title),
    ),
  );
}

21. Complete Responsive Dashboard Example

import 'package:flutter/material.dart';

void main() {
  runApp(const ResponsiveApp());
}

class ResponsiveApp extends StatelessWidget {
  const ResponsiveApp({super.key});

  @override
  Widget build(BuildContext context) {
    return MaterialApp(
      debugShowCheckedModeBanner: false,
      home: const ResponsiveDashboard(),
    );
  }
}

class ResponsiveDashboard extends StatelessWidget {
  const ResponsiveDashboard({super.key});

  Widget buildCard(String title, String value, IconData icon) {
    return Card(
      child: Padding(
        padding: const EdgeInsets.all(20),
        child: Row(
          children: [
            Icon(icon, size: 35),
            const SizedBox(width: 15),
            Expanded(
              child: Column(
                crossAxisAlignment: CrossAxisAlignment.start,
                children: [
                  Text(title),
                  const SizedBox(height: 8),
                  Text(
                    value,
                    style: const TextStyle(
                      fontSize: 22,
                      fontWeight: FontWeight.bold,
                    ),
                  ),
                ],
              ),
            ),
          ],
        ),
      ),
    );
  }

  @override
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(
        title: const Text('Responsive Dashboard'),
      ),
      body: LayoutBuilder(
        builder: (context, constraints) {
          final isSmall = constraints.maxWidth < 600;

          final cards = [
            buildCard('Users', '1,250', Icons.people),
            buildCard('Orders', '840', Icons.shopping_cart),
            buildCard('Revenue', '₹85,000', Icons.currency_rupee),
            buildCard('Pending', '24', Icons.pending),
          ];

          return SingleChildScrollView(
            padding: const EdgeInsets.all(16),
            child: isSmall
                ? Column(
                    children: [
                      for (final card in cards) ...[
                        card,
                        const SizedBox(height: 12),
                      ],
                    ],
                  )
                : Column(
                    children: [
                      Row(
                        children: [
                          Expanded(child: cards[0]),
                          const SizedBox(width: 12),
                          Expanded(child: cards[1]),
                        ],
                      ),
                      const SizedBox(height: 12),
                      Row(
                        children: [
                          Expanded(child: cards[2]),
                          const SizedBox(width: 12),
                          Expanded(child: cards[3]),
                        ],
                      ),
                    ],
                  ),
          );
        },
      ),
    );
  }
}

22. Responsive Login Form

LayoutBuilder(
  builder: (context, constraints) {
    final isMobile = constraints.maxWidth < 600;

    return Center(
      child: SizedBox(
        width: isMobile ? double.infinity : 450,
        child: Card(
          child: Padding(
            padding: const EdgeInsets.all(24),
            child: Column(
              mainAxisSize: MainAxisSize.min,
              children: [
                const Text(
                  'Login',
                  style: TextStyle(
                    fontSize: 28,
                    fontWeight: FontWeight.bold,
                  ),
                ),
                const SizedBox(height: 20),
                const TextField(
                  decoration: InputDecoration(
                    labelText: 'Email',
                    border: OutlineInputBorder(),
                  ),
                ),
                const SizedBox(height: 15),
                const TextField(
                  obscureText: true,
                  decoration: InputDecoration(
                    labelText: 'Password',
                    border: OutlineInputBorder(),
                  ),
                ),
                const SizedBox(height: 20),
                SizedBox(
                  width: double.infinity,
                  child: ElevatedButton(
                    onPressed: () {},
                    child: const Text('Login'),
                  ),
                ),
              ],
            ),
          ),
        ),
      ),
    );
  },
)

23. Responsive Navigation Example

A common responsive application pattern is to display a sidebar on larger screens and a simpler navigation control on smaller screens.

LayoutBuilder(
  builder: (context, constraints) {
    final isDesktop = constraints.maxWidth >= 900;

    if (isDesktop) {
      return Row(
        children: [
          SizedBox(
            width: 250,
            child: NavigationRail(
              destinations: const [
                NavigationRailDestination(
                  icon: Icon(Icons.home),
                  label: Text('Home'),
                ),
                NavigationRailDestination(
                  icon: Icon(Icons.settings),
                  label: Text('Settings'),
                ),
              ],
              selectedIndex: 0,
              onDestinationSelected: (index) {},
            ),
          ),
          const Expanded(
            child: Center(
              child: Text('Desktop Content'),
            ),
          ),
        ],
      );
    }

    return const Center(
      child: Text('Mobile Content'),
    );
  },
)

24. Responsive Image Layout

Images should generally be constrained instead of assigning unnecessarily large fixed dimensions.

Expanded(
  child: Image.network(
    'https://example.com/image.jpg',
    fit: BoxFit.cover,
  ),
)

Inside a responsive layout, Expanded can provide the available width while BoxFit.cover controls how the image is fitted within its constraints.

25. Flexible with Long Text

Row(
  children: [
    const Icon(Icons.info),
    const SizedBox(width: 8),
    Flexible(
      child: Text(
        'This is a long message that should not cause horizontal overflow on smaller screens.',
        softWrap: true,
      ),
    ),
  ],
)

This pattern is useful for messages, descriptions, notification text, product titles, and list items.

26. Using MainAxisSize for Compact Layouts

MainAxisSize.min allows a Row or Column to take only the space required by its children, subject to the incoming constraints.

Column(
  mainAxisSize: MainAxisSize.min,
  children: [
    const Text('Title'),
    const SizedBox(height: 8),
    ElevatedButton(
      onPressed: () {},
      child: const Text('Continue'),
    ),
  ],
)

MainAxisSize.max attempts to occupy the maximum available main-axis space, while MainAxisSize.min minimizes the amount of main-axis space used.

27. Avoiding Fixed Widths Everywhere

A common beginner mistake is assigning fixed widths to every widget.

// Less flexible
Container(
  width: 350,
  child: const Text('Content'),
)

A more flexible approach is to allow the parent layout to determine the available width.

Expanded(
  child: Container(
    padding: const EdgeInsets.all(16),
    child: const Text('Content'),
  ),
)

Fixed dimensions are still useful when the design genuinely requires a fixed size, but they should not be used unnecessarily for every element.

28. Responsive Spacing

Spacing can also be adjusted based on available width.

LayoutBuilder(
  builder: (context, constraints) {
    final spacing = constraints.maxWidth < 600 ? 12.0 : 24.0;

    return Padding(
      padding: EdgeInsets.all(spacing),
      child: const Text('Responsive Content'),
    );
  },
)

29. Responsive Grid Using GridView

A grid is useful for dashboards, product catalogs, galleries, and card-based interfaces.

GridView.builder(
  padding: const EdgeInsets.all(16),
  gridDelegate: const SliverGridDelegateWithMaxCrossAxisExtent(
    maxCrossAxisExtent: 300,
    crossAxisSpacing: 16,
    mainAxisSpacing: 16,
    childAspectRatio: 1.3,
  ),
  itemCount: 10,
  itemBuilder: (context, index) {
    return Card(
      child: Center(
        child: Text('Item ${index + 1}'),
      ),
    );
  },
)

Using a maximum cross-axis extent allows the number of columns to adjust as more horizontal space becomes available.

30. Flexible Layout with Nested Row and Column

Row(
  children: [
    Expanded(
      flex: 1,
      child: Container(
        padding: const EdgeInsets.all(16),
        color: Colors.blue,
        child: const Column(
          crossAxisAlignment: CrossAxisAlignment.start,
          children: [
            Text(
              'Profile',
              style: TextStyle(
                color: Colors.white,
                fontSize: 22,
              ),
            ),
            SizedBox(height: 10),
            Text(
              'User information',
              style: TextStyle(color: Colors.white),
            ),
          ],
        ),
      ),
    ),
    const SizedBox(width: 16),
    Expanded(
      flex: 2,
      child: Container(
        padding: const EdgeInsets.all(16),
        color: Colors.green,
        child: const Text(
          'Main content area',
          style: TextStyle(color: Colors.white),
        ),
      ),
    ),
  ],
)

31. Handling Small Screens

When a layout becomes too narrow, simply shrinking every widget may make the UI difficult to use. Instead, change the layout structure.

Widget buildResponsiveLayout(BuildContext context) {
  return LayoutBuilder(
    builder: (context, constraints) {
      if (constraints.maxWidth < 600) {
        return const Column(
          children: [
            Text('Mobile Header'),
            Text('Mobile Content'),
          ],
        );
      }

      return const Row(
        children: [
          Expanded(child: Text('Desktop Header')),
          Expanded(child: Text('Desktop Content')),
        ],
      );
    },
  );
}

32. Flexible Layout with Scrollable Content

When content can become larger than the available screen height, a scrolling widget such as ListView or SingleChildScrollView may be appropriate.

SingleChildScrollView(
  padding: const EdgeInsets.all(16),
  child: Column(
    children: [
      const Text('Header'),
      const SizedBox(height: 20),
      Container(height: 300, color: Colors.blue),
      const SizedBox(height: 20),
      Container(height: 300, color: Colors.green),
    ],
  ),
)

Be careful when placing Expanded or Flexible inside a vertically scrolling viewport. Such a viewport can provide unbounded vertical space, which can make flex-based remaining-space calculations invalid.

33. Common Expanded/Flexible Error

SingleChildScrollView(
  child: Column(
    children: [
      Expanded(
        child: Container(),
      ),
    ],
  ),
)

The above pattern can produce an unbounded-height flex error because the scroll view does not provide a finite maximum height in its scrolling direction.

A common solution is to remove the unnecessary Expanded and allow the content to determine its natural height, or restructure the layout so that the flex widget receives a finite constraint.

34. Flexible Layout Best Practices

  • Use Expanded when a child should fill its allocated remaining space.
  • Use Flexible when the child should be allowed to remain smaller.
  • Use Wrap when children may need to move onto multiple lines.
  • Use LayoutBuilder when a reusable widget should respond to its parent's constraints.
  • Use MediaQuery.sizeOf(context) when the overall application window size is relevant.
  • Prefer flexible constraints over unnecessary fixed dimensions.
  • Use scrolling widgets when content can exceed the available space.
  • Test layouts at different widths and heights.
  • Use meaningful breakpoints based on content requirements.
  • Keep text readable on both small and large screens.

35. Common Mistakes

Mistake 1: Putting Expanded Outside Row or Column

Expanded(
  child: Text('Incorrect usage'),
)

Expanded should be a descendant of a suitable Flex widget such as Row, Column, or Flex.

Mistake 2: Using Too Many Fixed Widths

Large fixed widths can cause overflow on smaller screens.

Mistake 3: Using Row for Too Many Items

If several items cannot fit horizontally, consider Wrap, ListView, or a responsive layout change.

Mistake 4: Ignoring Text Overflow

Long text inside a Row should generally be constrained using Expanded or Flexible.

Mistake 5: Incorrect Expanded Inside ScrollView

Do not automatically use Expanded inside a vertically scrolling Column. First understand the constraints provided by the scrollable parent.

36. Flexible Layout Design Strategy

  1. Start with the smallest practical layout.
  2. Identify which widgets need to grow.
  3. Use Expanded or Flexible for flexible space.
  4. Use Wrap when items may require multiple lines.
  5. Define layout changes based on available width.
  6. Use LayoutBuilder for component-level responsiveness.
  7. Use MediaQuery when application-level dimensions are required.
  8. Add scrolling when content can exceed the available height.
  9. Test the layout on multiple screen sizes.
  10. Remove unnecessary fixed dimensions.

37. Real-World Applications

  • Responsive login and registration screens
  • Shopping and e-commerce applications
  • Product grids
  • Admin dashboards
  • Profile pages
  • Chat applications
  • News applications
  • Food delivery applications
  • Banking dashboards
  • Responsive web applications
  • Tablet applications
  • Desktop Flutter applications

38. Interview Questions

Q1. What is the purpose of Expanded?

Expanded makes a child of a Row, Column, or Flex fill the available space along the main axis.

Q2. What is the difference between Expanded and Flexible?

Expanded uses tight constraints and requires its child to fill the allocated space. Flexible uses loose constraints by default and allows the child to be smaller than the allocated space.

Q3. What is the flex property?

The flex property determines the proportional share of remaining space allocated to flexible children.

Q4. When should you use Wrap?

Use Wrap when children may need to move onto multiple rows or columns instead of remaining on one line.

Q5. What is LayoutBuilder?

LayoutBuilder provides the constraints available to its child and allows the UI to change according to those constraints.

Q6. Why can Expanded cause an error inside a ScrollView?

A scrollable viewport can provide unbounded space along its scrolling axis. Expanded needs a finite amount of remaining space to calculate its allocation.

Q7. What is responsive design?

Responsive design means adapting the application's layout and presentation to different available screen sizes and constraints.

39. Practice Exercises

  1. Create a Row containing three cards using Expanded.
  2. Create a responsive layout that changes from Row to Column below a chosen width.
  3. Create a profile screen using Flexible for long user information.
  4. Create a responsive login form using LayoutBuilder.
  5. Create a product grid that adapts the number of columns to available width.
  6. Create a dashboard that displays two columns on larger screens and one column on smaller screens.
  7. Create a responsive tag/chip layout using Wrap.
  8. Create a responsive navigation layout for mobile and desktop.

40. Quick Revision

Widget/ConceptPurpose
RowHorizontal Flex layout
ColumnVertical Flex layout
FlexCustom one-dimensional layout
ExpandedFills allocated remaining space
FlexibleShares available space without requiring the child to fill it
SpacerCreates flexible empty space
WrapMoves children onto additional runs
LayoutBuilderBuilds UI according to parent constraints
MediaQueryProvides application/window dimension information
ListViewProvides efficient scrolling for lists
SingleChildScrollViewAllows a single child to scroll when required
MainAxisSizeControls how much main-axis space a Flex occupies

41. Useful Flutter Documentation

42. Flutter Training Resources

For structured Flutter learning, practical development training, and course information, visit the following resources:

43. Summary

Creating flexible and responsive layouts is an important Flutter skill. Row, Column, and Flex provide the basic one-dimensional layout system, while Expanded and Flexible help distribute available space. Wrap is useful when content needs to move across multiple lines. LayoutBuilder and MediaQuery can be used to make layout decisions based on available dimensions. Scrollable widgets should be introduced when content can exceed the available space.

A good responsive Flutter layout does not simply make everything smaller. Instead, it uses appropriate constraints, flexible widgets, spacing, wrapping, scrolling, and structural changes so that the interface remains usable across different screen sizes.


Key Formula: Flexible Flutter UI = Proper Constraints + Expanded/Flexible + Responsive Breakpoints + Wrap/Grid + Scrolling + Testing

whatsapp